<%
'----------------------------------------------------------------------
' Module	 : _kudzu.asp - Kudzu ASP Template Engine
' Author	 : Andrew F. Friedl @ TriLogic Industries, LLC
' Google	 : "TriLogic Industries, LLC"
' Created	 : 2006.05.09
' Revised	 : 2009.08.31
' Version	 : 1.0.7
' Copyright	: 2006,2007 TriLogic Industries, LLC
' License  	: Full license is granted for personal or commercial use
'          	: provided this header remains intact. This source code
'		: may not be sold or redistributed as part of a larger
'		: work without prior authorization of the author.
'----------:-----------------------------------------------------------
'          : Oh Mary conceived without sin,
'          : pray for use who have recourse to thee.
'----------------------------------------------------------------------
Dim Kudzu_Version: Kudzu_Version = "1.0.7"

'----------------------------------------------------------------------
' Class Based Plugins
'----------------------------------------------------------------------
'
' Flush all evaluated content to the ASP response stream.
'
Class CTPFlush
	Sub HandleTag( vNode )
		vNode.Engine.ContentFlush
	End Sub
End Class

'
' Writes time to execute to this point information into the template variables
' and evaluate the templated content.
'
Class CTPProfiler
	Sub HandleTag( vNode )
		Dim timeSpan
		vNode.Engine.StopTime = Timer()
		timeSpan = vNode.Engine.StopTime - vNode.Engine.StartTime
		vNode.Engine.PutValue "PageTime", FormatNumber(timeSpan,4)
		vNode.StackPush
		vNode.EvalNodes
		vNode.Engine.ContentReplaceFields
		vNode.StackPop
	End Sub
End Class

'
' Execute another page in the 'Server.Execute' tradition
'
Class CTPExecute
	Sub HandleTag( vNode )
		Dim sExpr
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "expr"
			Exit Sub
		End If
		sExpr = vNode.ParamItem(1)
		If Left( sExpr, 1 ) = "?" Then
			sExpr = Right( sExpr, Len( sExpr ) - 1 )
			sExpr = vNode.EvalParamString( sExpr )
		End If
		If sExpr = "" Then
			vNode.AppendTagError "empty url"
			Exit Sub
		End If
		On Error Resume Next
		vNode.Engine.ContentFlush
		vNode.StackPush
		Server.Execute sExpr
		If Err.Number <> 0 Then
			vNode.AppendTagError Err.Description
		End If
		vNode.StackPop
	End Sub
End Class

'
' Examines a specific value from the template engine and based upon the boolean value either
' writes either the true node or the false node into the content stream.  The true and false values
' are values obtained by evaluating values from the template engine.
'
Class CTPIIf
	Sub HandleTag( vNode )
		If vNode.ParamCount < 3 Then
			vNode.AppendTagError "value_id|true_id|false_id"
			Exit Sub
		End If
		If Not CBool( vNode.EvalParamString( vNode.ParamItem( 1 ) ) ) Then
			Exit Sub
		End If
		vNode.StackPush
		If CBool( vNode.EvalParamString( vNode.ParamItem( 1 ) ) ) Then
			vNode.Engine.ContentAppend vNode.EvalParamString( vNode.ParamItem( 2 ) )
		Else
			vNode.Engine.ContentAppend vNode.EvalParamString( vNode.ParamItem( 3 ) )
		End If
		vNode.StackPop
	End Sub
End Class

'
' Evaluates a boolean value from the template engine and if true, evaluates the nodes
' templated content and writes it to the output stream.
'
Class CTPIfTrue
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "value_id"
			Exit Sub
		End If
		If Not CBool( vNode.EvalParamString( vNode.ParamItem( 1 ) ) ) Then
			Exit Sub
		End If
		vNode.StackPush
		vNode.EvalNodes
		vNode.StackPop
	End Sub
End Class

'
' Evaluates a boolean value from the template engine and if false, evaluates the nodes
' templated content and writes it to the output stream.
'
Class CTPIfFalse
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "value_id"
			Exit Sub
		End If
		If CBool( vNode.EvalParamString( vNode.ParamItem( 1 ) ) ) Then
			Exit Sub
		End If
		vNode.StackPush
		vNode.EvalNodes
		vNode.StackPop
	End Sub
End Class

'
' Exposes the classical If..Then..Else programming facility to the template engine.  This
' component evaluates a particular variable in the engine and evaluates either the 'Then'
' or the 'Else' child nodes (if present), and writes the result to the output stream.
'
Class CTPIfThen
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "value_id"
			Exit Sub
		End If
		If CBool( vNode.EvalParamString( vNode.ParamItem( 1 ) ) ) Then
			EvalNode vNode, "then"
		else
			EvalNode vNode, "else"
		End If
	End Sub
	Sub EvalNode( vNode, sNode )
		Dim mNode
		Set mNode = vNode.LocateNode( sNode )
		If mNode Is Nothing Then Exit Sub
		mNode.StackPush
		mNode.EvalNodes
		mNode.StackPop
	End Sub
End Class

'
' Ignores all templated content and prevents it from becomming part
' of the final page result.
'
Class CTPIgnore
	Sub HandleTag( vNode )
	End Sub
End Class

'
' Replace all templated content with the value of a single value from within 
' the template engine.
'
Class CTPReplace
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "value_id"
			Exit Sub
		End If
		If vNode.ParamItem(1) = "" Then Exit Sub
		vNode.StackPush
		On Error Resume Next
		vNode.Engine.ContentAppend vNode.EvalParamString( vNode.ParamItem(1) )
		vNode.StackPop
	End Sub
End Class

'
' Evaluates all child content, then scans the result and replaces all field values (denoted
' by {{value_id}} ) with a corresponding value from the template engine and adds the resulting
' content to output.
'
Class CTPSubst
	Sub HandleTag( vNode )
		vNode.StackPush
		vNode.EvalNodes
		vNode.StackPop
		vNode.Engine.ContentReplaceFields
	End Sub
End Class

'
' Iterates a block of templated content using an array (stored in the template engine)
' to control the maximum number of times iteration will occur.  This component expects
' the existence of the array and an iterator to be setup within the engine having
' proper names and types.  With each iteration this component passes the Nth array element
' to the iterator.  Iteration continues as long as there are array elements remaining and 
' the iterator returns true.
'
Class CTPForArray
	Sub HandleTag( vNode )
		Dim Idx, vParam, sFlag, oIter
		' Insure the parameter count
		If vNode.ParamCount < 2 Then
			vNode.AppendTagError "value_id|iterator_id"
			Exit Sub
		End If
		' insure the iterator exists
		If Not vNode.Engine.HasIterator( vNode.ParamItem(2) ) Then
			vNode.AppendTagError "undefined iterator (" & vNode.ParamItem(2) & ")"
			Exit Sub
		End If
		
		' retrieve the iterator
		Set oIter = vNode.Engine.GetIterator( vNode.ParamItem(2) )
		' evaluate the array variable
		vParam = vNode.EvalParamString( vNode.ParamItem(1) )
		If Not IsArray(vParam) Then
			vNode.AppendTagError "not an array (" & vNode.ParamItem(1) & ")"
			Exit Sub
		End If
		' template evaluation
		vNode.StackPush
		
		For Idx = 1 To UBound(vParam)
			If Not oIter.GoItem(vNode,vParam(Idx)) Then
				Exit For
			End If
			vNode.StackPush
			vNode.EvalNodes
			vNode.StackPop
		Next
		vNode.StackPop
	End Sub
End Class

'
' Iterates a block of templated content using a collection(stored in the template engine)
' to control the maximum number of times iteration will occur.  This component expects
' the existence of the collection and an iterator to be setp within the engine having
' proper names and types.  With each iteration this component passes a collection element
' to the iterator.  Iteration continues as long as there are collection elements remaining 
' and the iterator returns true.
'
Class CTPForEach
	Sub HandleTag( vNode )
		Dim vItem, vParam, sFlag, oIter
		' insure the parameter count
		If vNode.ParamCount < 2 Then
			vNode.AppendTagError "value_id|iterator_id"
			Exit Sub
		End If
		' insure the iterator exists
		If Not vNode.Engine.HasIterator( vNode.ParamItem(2) ) Then
			vNode.AppendTagError "undefined iterator (" & vNode.ParamItem(2) & ")"
			Exit Sub
		End If
		' retrieve the iterator
		Set oIter = vNode.Engine.GetIterator( vNode.ParamItem(2) )
		' evaluate the node
		vNode.StackPush
	    On Error Resume Next
		For Each vItem In vNode.EvalParamObject( vNode.ParamItem(1) )
			If Err.Number <> 0 Then
				vNode.AppendTagError Err.Description
				Exit For
			End If
			If Not oIter.GoItem( vNode, vItem ) Then
				Exit For
			End If
			vNode.StackPush
			vNode.EvalNodes
			vNode.StackPop
		Next
		vNode.StackPop
	End Sub
End Class

'
' Iterates a block of templated content using a pure iterator from the template engine.
' In this component, the iterator is located and the GoFirst method is called.  Iteration
' of content occurs for this and for all subsequent GoNext calls that return true.
'
' This component is dangerous due to the non-specific nature of the iteration.  It is
' however useful for iterating through record sets, or other information for which
' creating an array is too costly due to memory or timing overhead concerns.
'
' WARNING: It is best to never catch an exception while under the influence of this 
' component - you might iterate indefinitely.
'
Class CTPIterate
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "iterator_id"
			Exit Sub
		End If
		If Not vNode.Engine.HasIterator( vNode.ParamItem(1) ) Then
			vNode.AppendTagError "undefined iterator (" & vNode.ParamItem(1) & ")"
			Exit Sub
		End If		
		Dim oIter, bMore, bUseTag
		Set oIter = vNode.Engine.GetIterator( vNode.ParamItem(1) )
		bMore = oIter.GoFirst( vNode )
		vNode.StackPush
		While bMore
			vNode.StackPush
			vNode.EvalNodes
			vNode.StackPop
			bMore = oIter.GoNext( vNode )
		Wend
		vNode.StackPop
	End Sub
End Class

'
' Exposes the traditional Select Case functionality to the template engine.
' This component evaluates a particular value from within the template engine
' and then evaluates either the node that exactly matches the value of the result
' or the 'else' node (provided there is one).
'
Class CTPCase
	Sub HandleTag( vNode )
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "value_id"
			Exit Sub
		End If
		Dim NodeName
		NodeName = vNode.EvalParamString( vNode.ParamItem( 1 ) )
		EvalCase vNode, NodeName
	End Sub
	Sub EvalCase( vNode, sNode )
		Dim mNode
		Set mNode = vNode.LocateNode( sNode )
		If mNode Is Nothing Then
			Set mNode = vNode.LocateNode( "else" )
		End If
		If mNode Is Nothing Then Exit Sub
		mNode.StackPush
		mNode.EvalNodes
		mNode.StackPop
	End Sub
End Class

'
' This component exposes the ability of the template engine to generate a tree of
' data nodes using the templated content for output.  Upon execution this component
' retrieves the named value from the engine which must be of Type 'CTPTree'.  For each
' node in the tree, this componet will attempt to evaluate a templated node corresponding
' to the tree node type.
' WARNING: Self referential trees will cause an infinite loop.
'
Class CTPTree
	Sub HandleTag( vNode )
		Dim vTree
		If vNode.ParamCount < 1 Then
			vNode.AppendTagError "tree value?"
			Exit Sub
		End If
		On Error Resume Next
		Set vTree = vNode.Engine.GetObjectValue(vNode.ParamItem(1))
		If vTree.NodeCount = 0 Then
			EvalNode vNode, "EmptyTree"
			vNode.StackPop
			Exit Sub
		End If
		EvalTreeNode vNode, vTree
		If Err.Number <> 0 Then
			vNode.AppendTagError Err.Description
		End If
	End Sub
	Sub EvalTreeNode( vNode, vTreeNode )
		Dim Idx, tNode
		vNode.StackPush
		If LCase(vTreeNode.NodeType) = "tree" Then
			EvalTreeNodeAttr vNode, vTreeNode
			EvalNode vNode, "header"
			EvalTreeChildNodes vNode, vTreeNode
			EvalTreeNodeAttr vNode, vTreeNode
			EvalNode vNode, "footer"
		Else
			Set tNode = vNode.LocateNode( vTreeNode.NodeType )
			If tNode Is Nothing Then
				vNode.AppendTagError "missing tree node(" & vTreeNode.NodeType & "," & vTree.NodeType & ")"
			Else
				EvalTreeNodeAttr vNode, vTreeNode
				EvalNode tNode, "header"
				EvalNode tNode, "Body"
				EvalTreeChildNodes vNode, vTreeNode
				EvalTreeNodeAttr vNode, vTreeNode
				EvalNode tNode, "footer"				
			End If
		End If
		vNode.StackPop		
	End Sub
	Sub EvalTreeChildNodes( vNode, vTreeNode )
		Dim Idx
		For Idx = 1 To vTreeNode.NodeCount
			EvalTreeNode vNode, vTreeNode.Node(Idx)
		Next
	End Sub
	Sub EvalTreeNodeAttr( vNode, vTreeNode )
		Dim arrAttr, Idx
		arrAttr = vTreeNode.AttributeKeys()
		For Idx = 1 To UBound(arrAttr)
			vNode.Engine.PutValue arrAttr(Idx), vTreeNode.Attribute(arrAttr(Idx))
		Next
	End Sub
	Sub EvalNode( vNode, node_id )
		Dim mNode
		Set mNode = vNode.LocateNode( node_id )
		If mNode Is Nothing Then Exit Sub
		mNode.EvalNodes
	End Sub
End Class

'
' Decrement a numeric value within the template engine
'
Class CTPDecr
	Public Sub HandleTag(vNode)
		Dim ValName, CurValue
		If vNode.ParamCount < 1 Then
			vNode.AppendError "value_id"
		End If
		ValName = vNode.ParamItem(1)
		If vNode.Engine.HasValue(ValName) Then
			CurValue = vNode.Engine.GetValue(ValName)
			CurValue = CLng(CurValue) - 1
		Else
			If vNode.ParamCount > 1 Then
				CurValue = CLng(vNode.ParamItem(2))
			Else
				CurValue = 0
			End If
		End If
		vNode.Engine.PutValue ValName, CurValue
	End Sub
End Class

'
' Increment a numeric value within the template engine
'
Class CTPIncr
	Public Sub HandleTag(vNode)
		Dim ValName, CurValue
		If vNode.ParamCount < 1 Then
			vNode.AppendError "value_id"
		End If
		ValName = vNode.ParamItem(1)
		If vNode.Engine.HasValue(ValName) Then
			CurValue = vNode.Engine.GetValue(ValName)
			CurValue = CLng(CurValue) + 1
		Else
			If vNode.ParamCount > 1 Then
				CurValue = CLng(vNode.ParamItem(2))
			Else
				CurValue = 0
			End If
		End If
		vNode.Engine.PutValue ValName, CurValue
	End Sub
End Class

'
' Set One of More Values within the Template Engine
'
Class CTPSetValue
	Public Sub HandleTag(vNode)
		Dim ValName, CurValue, Idx, Jdx
		If vNode.ParamCount < 1 Then
			vNode.AppendError "value_id??values&&|..."
		End If
		For Idx = 1 To vNode.ParamCount
			ValName = vNode.ParamItem(Idx)
			CurValue = Split(ValName, "??")
			' name of the value to be set
			ValName = CurValue(0)
			If UBound(CurValue) > 0 Then
				CurValue = CurValue(1)
				CurValue = Split(CurValue, "&&")
				If UBound(CurValue) < 1 Then
					CurValue = CurValue(0)
					CurValue = vNode.Engine.ReplaceFields(CStr(CurValue))
				Else
					ReDim Preserve CurValue(UBound(CurValue) + 1)
					For Jdx = UBound(CurValue) To 1 Step -1
						CurValue(Jdx) = CurValue(Jdx - 1)
						If Left(CurValue(Jdx), 1) = "$" Then
							CurValue(Jdx) = vNode.Engine.ReplaceFields(CStr(CurValue(Jdx)))
						End If
					Next
					CurValue(0) = ""
				End If
			Else
				CurValue = ""
			End If
			
			If Left(ValName, 1) = "$" Then
				ValName = vNode.Engine.ReplaceFields(ValName)
			End If
			
			' put the most recent value
			vNode.Engine.PutValue ValName, CurValue
		Next
	End Sub
End Class
Class CTPUnSetValue
	Public Sub HandleTag(vNode)
		Dim ValName, CurValue, Idx, Jdx
		If vNode.ParamCount < 1 Then
			vNode.AppendError "value_id|..."
		End If
		For Idx = 1 To vNode.ParamCount
			ValName = vNode.ParamItem(Idx)
			vNode.Engine.UnPutValue ValName
		Next
	End Sub
End Class

'
' Insert Spaces into the output stream
'
Class CTPSpace
	Public Sub HandleTag(vNode)
		Dim Count: Count = 1
		If vNode.ParamCount > 0 Then
			Count = CLng(vNode.ParamItem(1))
		End If
		vNode.Engine.ContentAppend String(Count, " ")
	End Sub
End Class
Class CTPTab
	Public Sub HandleTag(vNode)
		Dim Count: Count = 1
		If vNode.ParamCount > 0 Then
			Count = CLng(vNode.ParamItem(1))
		End If
		vNode.Engine.ContentAppend String(Count, vbTab)
	End Sub
End Class
Class CTPCRLF
	Public Sub HandleTag(vNode)
		Dim Count: Count = 1
		If vNode.ParamCount > 0 Then
			Count = CLng(vNode.ParamItem(1))
		End If
		vNode.Engine.ContentAppend String(Count, vbCrLf)
	End Sub
End Class

'
' Cycle a named value through a series of supplied values.
'
Class CTPCycle
	Public Sub HandleTag(vNode)
		Dim ValName, CurValue, Idx, Jdx
		If vNode.ParamCount < 3 Then
			vNode.AppendError "value_id|Alt1|Alt2..."
		End If
		' name of the value to be set
		ValName = vNode.ParamItem(1)
		CurValue = vNode.ParamItem(2)
		' check if the engine has this value set
		If vNode.Engine.HasValue(ValName) Then
			CurValue = vNode.Engine.GetValue(ValName)
			Jdx = 2
			For Idx = 2 To vNode.ParamCount
				If vNode.ParamItem(Idx) = CurValue Then
					Jdx = Idx
				End If
			Next
			If Jdx >= vNode.ParamCount Then
				CurValue = vNode.ParamItem(2)
			Else
				CurValue = vNode.ParamItem(Jdx + 1)
			End If
		End If
		' put the most recent value
		vNode.Engine.PutValue ValName, CurValue
	End Sub
End Class

'
' Capture template output to a variable
'
Class CTPRecord
	Public Sub HandleTag(vNode)
		Dim ValName, bCapture
		If vNode.ParamCount < 3 Then
			vNode.AppendError "value_id|multiple"
		End If
		ValName = vNode.ParamItem(1)
		bCapture = CBool(vNode.ParamItem(2))
		vNode.StackPush
		vNode.EvalNodes
		If Not vNode.Engine.HasValue( ValName ) Then CapMult = True
		If CapMult Then
			vNode.Engine.PutValue ValName, vNode.Engine.Content
		End If
		vNode.StackPop		
	End Sub
End Class

'----------------------------------------------------------------------
' Class Template Node
'----------------------------------------------------------------------
Class CTemplateNode
	Dim iid
	Dim mStartTag, mStopTag, mContent, mEvalProc
	Dim mEngine, mNodes, mParams
	Sub Class_Initialize()
		mStartTag="": mStopTag="": mContent="": mEvalProc=""
		mNodes = Array(""): mParams = Array("")
		Set mEngine = Nothing
	End Sub
	Property Get Engine()
		Set Engine = mEngine
	End Property
	Property Set Engine(Value)
		Dim Idx
		Set mEngine = Value
		For Idx = 1 To Ubound(mNodes)
			Set mNodes(Idx).Engine = mEngine
		Next
	End Property
	Property Get ID()
		ID = iid
	End Property
	Property Let ID(Value)
		iid = LCase(Value)
	End Property
	Property Get StartTag()
		StartTag = mStartTag
	End Property
	Property Let StartTag(Value)
		mStartTag = LCase(Value)
	End Property
	Property Get StopTag()
		StopTag = mStopTag
	End Property
	Property Let StopTag(Value)
		mStopTag = LCase(Value)
	End Property
	Property Get Content()
		Content = mContent
	End Property
	Property Let Content(Value)
		mContent = Value
	End Property
	Property Get EvalProc()
		EvalProc = mEvalProc
	End Property
	Property Let EvalProc(Value)
		mEvalProc = Value
	End Property
	Public Property Get NodeItem(Index)
		Set NodeItem = mNodes(Index)
	End Property
	Property Get NodeCount()
		NodeCount = UBound(mNodes)
	End Property
	Sub NodeAppend(vNode)
		ReDim Preserve mNodes(UBound(mNodes) + 1)
		Set mNodes(UBound(mNodes)) = vNode
	End Sub
	Public Property Get ParamCount()
		ParamCount = Ubound(mParams)
	End Property
	Property Get ParamItem(Index)
		ParamItem = mParams(Index)
	End Property
	Sub ParamAdd( sValue )
		Redim Preserve mParams(Ubound(mParams)+1)
		mParams(Ubound(mParams)) = sValue
	End Sub
	Sub EvalProcString()
		On Error Goto 0
		If mEvalProc <> "" Then
			If mEngine.HasHandler( mEvalProc ) Then
				mEngine.GetHandler( mEvalProc ).HandleTag Me
			Else
				mEngine.GetHandler( "CustomHandler" ).HandleTag Me
			End If
		End If
	End Sub
	Function EvalParamString( sParam )
		If mEngine.HasValue( sParam ) Then
			EvalParamString = mEngine.GetValue( sParam )
		Else
			EvalParamString = Eval( sParam )
		End If
	End Function
	Function EvalParamObject( sParam )
		If mEngine.HasValue( sParam ) Then
			Set EvalParamObject = mEngine.GetObjectValue( sParam )
		Else
			Set EvalParamObject = Eval( sParam )
		End If
	End Function
	Sub EvalNode()
		If iid = "" Or mEvalProc = "" Then
			mEngine.ContentAppend mStartTag
			mEngine.ContentAppend mContent
			EvalNodes
			mEngine.ContentAppend mStopTag
		Else
			On Error Resume Next
			EvalProcString
			If Err.Number <> 0 Then
				mEngine.ContentAppend Err.Description
			End If
		End If
	End Sub
	Sub EvalNodeID( node_id )
		Dim mNode
		Set mNode = LocateNode( node_id )
		If mNode Is Nothing Then Exit Sub
		mNode.EvalNodes
	End Sub
	Sub EvalNodes()
		Dim Idx
		For Idx = 1 To UBound(mNodes)
			mNodes(Idx).EvalNode
		Next
	End Sub
	Sub StackPush( )
		mEngine.ContentPush
	End Sub
	Sub StackPop( )
		mEngine.ContentAppend mEngine.ContentPop()
	End Sub
	Sub AppendText(sText)
		Dim vNode: Set vNode = New CTemplateNode
		vNode.Content = sText
		NodeAppend vNode
	End Sub
	Sub AppendError( sMsg )
		AppendContent "<i><b>Error:</b>" & mEvalProc
		If sMsg <> "" Then AppendContent "|" & sMsg
		AppendContent "</i>"
	End Sub
	Sub AppendContent( sContent )
		mEngine.ContentAppend sContent
	End Sub
	Sub AppendTagError( sMSg )
		StackPush
		AppendError sMsg
		StackPop
	End Sub
	Function LocateNode( sID )
		Dim Idx
		For Idx = 1 to Ubound(mNodes)
			If LCase(mNodes(Idx).ID) = LCase(sID) Then
				Set LocateNode = mNodes(Idx)
				Exit Function
			End If
		Next
		Set LocateNode = Nothing
	End Function
End Class

'----------------------------------------------------------------------
' HTML Template Compiler
'----------------------------------------------------------------------
Class CTemplateCompiler
	Private mRxID, mRxTag, mRxDir
	Private mParseLevel
	Private mParseStack
	Private RGX_ID
	Private RGX_TAG
	Private RGX_DIR
	Private mDebug
	Private mScrubTags
	Private mParent

	Sub Class_Initialize()
		InitParseStack
		RGX_TAG = "<!--\[[^\]]+\]-->"
		Set mRxTag = new RegExp
		mRxTag.IgnoreCase = True
		mRxTag.Global = True
		mRxTag.MultiLine = False
		mRxTag.Pattern = RGX_TAG
		mDebug = False
		mScrubTags = True
		Set mParent = Nothing
	End Sub

	Property Get Debug()
		Debug = mDebug
	End Property
	Property Let Debug(Value)
		mDebug = Value
	End Property
	Property Get ScrubTags()
		ScrubTags = mScrubTags
	End Property
	Property Let ScrubTags(Value)
		mScrubTags = Value
	End Property
	Property Get Parent
		Set Parent = mParent
	End Property
	Property Set Parent( node )
		Set mParent = node
	End Property

	Sub InitParseStack()
		mParseStack = Array(New CTemplateNode)
		Set mParseStack(0).Engine = Me
		mParseStack(0).ID="_root"
		mParseLevel = 0
	End Sub

	Sub ParsePush(Value)
		mParseLevel = mParseLevel + 1
		ReDim Preserve mParseStack(mParseLevel)
		Set mParseStack(mParseLevel) = Value
	End Sub

	Function ParsePop()
		Set ParsePop = mParseStack(mParseLevel)
		mParseLevel = mParseLevel - 1
		ReDim Preserve mParseStack(mParseLevel)
	End Function

	Function ParsePeek()
		Set ParsePeek = mParseStack(mParseLevel)
	End Function

	Property Get ParseLevel()
		ParseLevel = mParseLevel
	End Property

	Function ParseFile( sFileName )
		Dim inp, tso, fso
		Set fso = Server.CreateObject("Scripting.FileSystemObject")
		On Error Resume next
		Set tso = fso.OpenTextFile( sFileName, 1, False)
		If Err.Number <> 0 Then
			Response.Write Err.Description & "<BR />"
			Response.Write sFileName
			Response.End
		End If
		inp = tso.ReadAll: tso.Close
		Set tso = Nothing: Set fso = Nothing
		Set ParseFile = ParseTemplate(inp)
	End Function

	Function ParseTemplate(sTemplate)
		Dim Matches, Index, LastOffset, Obj, LenTemplate
		InitParseStack
		Set Matches = mRxTag.Execute(sTemplate)
		LastOffset = 1
		LenTemplate = Len(sTemplate)
		For Index = 0 To Matches.Count - 1
			If Matches(Index).FirstIndex - LastOffset >= 0 Then
				ParsePeek().AppendText Mid( sTemplate, LastOffset, (Matches(Index).FirstIndex - LastOffset) + 1 )
			End If
			HandleTagMatch Matches(Index)
			LastOffset = Matches(Index).FirstIndex + Matches(Index).Length + 1
		Next
		If LastOffset < LenTemplate Then
			ParsePeek().AppendText Right(sTemplate, (LenTemplate - LastOffset) + 1 )
		End If
		Set ParseTemplate = mParseStack(0)
	End Function

	Function IsFormalEndTag(sValue)
		IsFormalEndTag = (Left(sValue, 6) = "<!--[/")
	End Function

	Function IsTermedTag(sValue)
		IsTermedTag = (Right(sValue, 5) = "/]-->")
	End Function

	Sub HandleTagMatch(oMatch)
		If IsFormalEndTag(oMatch.Value) Then
			ParseEndTag oMatch
		ElseIf IsTermedTag(oMatch.Value) Then
			ParseTermedTag oMatch
		Else
			ParseBeginTag oMatch
		End If
	End Sub

	Sub ParseTagProperties(oMatch, oNode, setID)
		Dim M, SP, Idx, Temp

		If mDebug Or Not mScrubTags Then
			oNode.StartTag = oMatch.Value
		Else
			oNode.StartTag = ""
		End If

		' STRIP INITIAL "<!--[/?" AND FINAL "/]-->"
		Temp = oMatch.Value
		Temp = Mid(Temp,6,Len(Temp)-9)

		If Left(Temp,1) = "/" Then
			Temp = Right(Temp,Len(Temp)-1)
		ElseIf Right(Temp,1) = "/" Then
			Temp = Left(Temp,Len(Temp)-1)
		End If

		SP = Split(Temp,"|")
		oNode.ID = SP(0)
		oNode.EvalProc = SP(0)
		For Idx = 1 To UBound(SP)
			oNode.ParamAdd SP(Idx)
		Next

	End Sub

	Sub ParseBeginTag(oMatch)
		Dim oNode
		If mDebug Then
			WriteOutput Right( "00000" & mParseLevel, 5 ) & ": " & DString(mParseLevel," | ") & Server.HtmlEncode( oMatch.Value ) & "<br>" & vbCrLf
		End If
		Set oNode = New CTemplateNode
		Set oNode.Engine = Me
		ParseTagProperties oMatch, oNode, True
		ParsePush oNode
	End Sub

	Sub ParseEndTag(oMatch)
		Dim oNode, ID: ID = oMatch.Value
		ID = LCase(Trim(Mid(ID, 7, Len(ID) - 10)))
		While (ParsePeek().ID <> ID And ParseLevel > 0)
			Set oNode = ParsePop()
			ParsePeek().NodeAppend oNode
		Wend
		If ParsePeek().ID = ID Then
			Set oNode = ParsePeek()
			oNode.StopTag = oMatch.Value
			If ParseLevel > 0 Then
				ParsePop
				ParsePeek().NodeAppend oNode
			End If
		End If
		If mDebug Then
			WriteOutput Right( "00000" & mParseLevel, 5 ) & ": " & DString(mParseLevel," | ") & Server.HtmlEncode( oMatch.Value ) & "<br>" & vbCrLf
		End If
	End Sub

	Sub ParseTermedTag(oMatch)
		Dim oNode: Set oNode = New CTemplateNode
		Set oNode.Engine = Me
		ParseTagProperties oMatch, oNode, False
		ParsePeek().NodeAppend oNode
		If mDebug Then
			WriteOutput Right( "00000" & mParseLevel, 5 ) & ": " & DString(mParseLevel," | ") & Server.HtmlEncode( oMatch.Value ) & "<br>" & vbCrLf
		End If
	End Sub

	Sub WriteOutput( sContent )
		If mParent Is Nothing Then
			Response.Write sContent
		Else
			mParent.ENGINE.ContentAppend sContent
		End IF
	End Sub

	Function DString(iCount, sRep )
		Dim IDx, Result
		For Idx = 1 To iCount
			Result = Result & sRep
		NExt
		DString = Result
	End Function

End Class

'----------------------------------------------------------------------
' Runtime Template Engine Factory
'----------------------------------------------------------------------
Class CKudzuEngineFactory
	Function CreateEngine()
		Set CreateEngine = New CTemplateEngine
	End Function
	Function CreateChildEngine( vNode )
		Set CreateChildEngine = CreateEngine()
		Set CreateChildEngine.Parent = vNode
	End Function
End Class

'----------------------------------------------------------------------
' Runtime Template Engine - BEGINS
'----------------------------------------------------------------------
Class CTemplateEngine
	Private mRxFld
	Private mRunStack
	Private mRunLevel
	Private mNodeTree
	Private mDebug
	Private mScrubTags
	Private mVersion
	Private mHandlers
	Private mValues
	Private mIterators
	Private mTimeStart
	Private mTimeEnd
	Private mParent
	Private mFactory

	Sub Class_Initialize()
		mTimeStart = Timer()
		mTimeEnd = mTimeStart
		Set mRxFld = new RegExp
		mRxFld.IgnoreCase = True
		mRxFld.Global = True
		mRxFld.MultiLine = False
		mRxFld.Pattern = "\{\{[^}]+\}\}"
		mDebug = False
		mScrubTags = True
		mVersion = Kudzu_Version
		Set mFactory = New CKudzuEngineFactory
		Set mParent = Nothing
		Set mHandlers = Server.CreateObject("Scripting.Dictionary")
		Set mValues = Server.CreateObject("Scripting.Dictionary")
		Set mIterators = Server.CreateObject("Scripting.Dictionary")
		InstallHandlers
		PutValue "Kudzu_Version", mVersion
		ClassReset
	End Sub

	Sub InstallHandlers()
		SetHandler "Case", New CTPCase
		SetHandler "CrLf", New CTPCrLf
		SetHandler "Cycle", New CTPCycle
		SetHandler "Decr", New CTPDecr
		SetHandler "Execute", New CTPExecute
		SetHandler "Flush", New CTPFlush
		SetHandler "ForArray", New CTPForArray
		SetHandler "ForEach", New CTPForEach
		SetHandler "IIf", New CTPSubst
		SetHandler "If", New CTPIfThen
		SetHandler "IfTrue", New CTPIfTrue
		SetHandler "IfFalse", New CTPIfFalse
		SetHandler "Ignore", New CTPIgnore
		SetHandler "Incr", New CTPIncr
		SetHandler "Iterate", New CTPIterate
		SetHandler "Record", New CTPRecord
		SetHandler "Replace", New CTPReplace
		SetHandler "Profiler", New CTPProfiler
		SetHandler "SetValue", New CTPSetValue
		SetHandler "Space", New CTPSpace
		SetHandler "Subst", New CTPSubst
		SetHandler "Tab", New CTPTab
		SetHandler "Tree", New CTPTree
		SetHandler "UnSetValue", New CTPUnSetValue
	End Sub

	Property Let StartTime( Value )
		mTimeStart = Value
	End Property
	Property Get StartTime()
		StartTime = mTimeStart
	End Property
	Property Let StopTime( Value )
		mTimeEnd = Value
	End Property
	Property Get StopTime()
		StopTime = mTimeEnd
	End Property
	Property Let Debug( Value )
		mDebug = Value
	End Property
	Property Get Debug()
		Debug = mDebug
	End Property
	Property Get ScrubTags()
		ScrubTags = mScrubTags
	End Property
	Property Let ScrubTags(Value)
		mScrubTags = Value
	End Property

	Sub ClassReset()
		Set mNodeTree = New CTemplateNode
		Set mNodeTree.Engine = Me
		mNodeTree.Content = "No Content"
		mRunStack = Array( "")
		mRunLevel = 0
	End Sub

	Property Get Parent
		Set Parent = mParent
	End Property
	Property Set Parent( vNode )
		Set mParent = vNode
	End Property

	Property Get Factory
		Set Factory = mFactory
	End Property
	Property Set Factory( oFactory )
		Set mFactory = oFactory
	End Property
	
	Property Get Iterators()
		Set Iterators = mIterators
	End Property
	Function HasIterator( sName )
		HasIterator = mIterators.Exists( LCase(sName) )
	End Function
	Function GetIterator( sName )
		Set GetIterator = mIterators( LCase(sName) )
	End Function
	Sub PutIterator( sName, oIter )
		Dim sKey: sKey = LCase(sName)
		If mIterators.Exists( sKey ) Then
			mIterators.Remove sKey
		End If
		mIterators.Add sKey, oIter
	End Sub

	Property Get Handlers()
		Set Handlers = mHandlers
	End Property
	Function GetHandler( sName )
		Set GetHandler = mHandlers( LCase(sName) )
	End Function
	Sub SetHandler( sName, oHandler )
		Dim sKey: sKey = LCase( sName )
		If mHandlers.Exists( sKey ) Then
			mHandlers.Remove( sKey )
		End If
		If Not oHandler Is Nothing Then
			mHandlers.Add sKey, oHandler
		End If
	End Sub
	Function HasHandler( sName )
		HasHandler = mHandlers.Exists( LCase(sName ) )
	End Function

	Property Get Values()
		Set Values = mValues
	End Property
	Function GetValue( sName )
		GetValue = mValues( LCase( sName ) )
	End Function
	Function GetObjectValue( sName )
		Set GetObjectValue = mValues( LCase( sName ) )
	End Function
	Property Get HasValue( sName )
		HasValue = mValues.Exists( LCase( sName ) )
	End Property
	Sub PutValue( sName, vValue )
		Dim sKey: sKey = LCase( sName )
		If mValues.Exists( sKey ) Then
			mValues.Remove sKey
		End If
		mValues.Add sKey, vValue
	End Sub

	Sub ParseFile( sFileName )
		Dim T_COMPILER
		mTimeStart = Timer()
		Set T_COMPILER = New CTemplateCompiler
		T_COMPILER.Debug = mDebug
		T_COMPILER.ScrubTags = mScrubTags
		Set T_COMPILER.Parent = mParent
		Me.ClassReset
		Set mNodeTree = T_COMPILER.ParseFile(sFileName)
		If mDebug Then Exit Sub
		Set mNodeTree.Engine = Me
	End Sub

	Sub ParseTemplate(sTemplate)
		Dim T_COMPILER
		mTimeStart = Timer()
		Set T_COMPILER = New CTemplateCompiler
		T_COMPILER.Debug = mDebug
		T_COMPILER.ScrubTags = mScrubTags
		Set T_COMPILER.Parent = mParent
		Me.ClassReset
		Set mNodeTree = T_COMPILER.ParseTemplate(sTemplate)
		If mDebug Then Exit Sub
		Set mNodeTree.Engine = Me
	End Sub

	Sub EvalTemplate()
		If mDebug Then Exit Sub
		mNodeTree.EvalNode
		WriteOutput mRunStack(0)
		mTimeEnd = Timer()
		If mParent Is Nothing Then
			WriteOutput VbCrLf & "<!-- Rendered by Kudzu " & mVersion
			WriteOutput ", " & FormatNumber(mTimeEnd - mTimeStart,4) & " seconds -->"
		End If
	End Sub
	Sub WriteOutput( sContent )
		If mParent Is Nothing Then
			Response.Write sContent
		Else
			ME.Parent.ENGINE.ContentAppend sContent
		End IF
	End Sub

	Function ContentLevel()
		ContentLevel = mRunLevel
	End Function
	Sub ContentPush()
		mRunLevel = mRunLevel + 1
		ReDim Preserve mRunStack(mRunLevel)
		mRunStack(mRunLevel) = ""
	End Sub
	Property Get Content()
		Content = mRunStack(mRunLevel)
	End Property
	Property Let Content(Value)
		mRunStack(mRunLevel) = Value
	End Property
	Function ContentPop()
		ContentPop = mRunStack(mRunLevel)
		If mRunLevel < 1 Then Exit Function
		mRunLevel = mRunLevel - 1
		ReDim Preserve mRunStack(mRunLevel)
	End Function
	Sub ContentAppend(sText)
		mRunStack(mRunLevel) = mRunStack(mRunLevel) & sText
	End Sub
	Sub ContentFlush( )
		Dim Idx
		For Idx = 0 To mRunLevel
			WriteOutput mRunStack(Idx)
			mRunStack(Idx) = ""
		Next
		If Not mParent is Nothing Then
			mParent.Engine.ContentFlush
		End If
	End Sub

	Sub ContentReplaceFields()
		Me.Content = ReplaceFields( Me.Content )
	End Sub
	Function GetFieldTags( sValue )
		Set GetFieldTags = mRxFld.Execute( sValue )
	End Function
	Function EvalParamString( sParam )
		If HasValue( sParam ) Then
			EvalParamString = GetValue( sParam )
		Else
			EvalParamString = Eval( sParam )
		End If
	End Function
	Function ReplaceFields( sValue )
		Dim LastOffset, Idx, M
		Dim Result, Tag
		Result = "": LastOffset = 1
		Set M = GetFieldTags( sValue )
		'On Error Resume Next
		If M.Count > 0 Then
			On Error Resume Next
			For Idx = 0 to M.Count - 1
				If M(Idx).FirstIndex - LastOffset >= 0 Then
					Result = Result & Mid(sValue, LastOffset, (M(Idx).FirstIndex - LastOffset) + 1)
				End If
				Tag = Trim( Mid( M(Idx).Value, 3, M(Idx).Length - 4 ) )
				Result = Result & EvalParamString( Tag )
				LastOffset = M(Idx).FirstIndex + M(Idx).Length + 1
			Next
			If LastOffset < Len(sValue) Then
				Result = Result & Right( sValue, (Len(sValue)-LastOffset) + 1)
			End If
		Else
			Result = sValue
		End If
		ReplaceFields = Result
	End Function

End Class

'----------------------------------------------------------------------
' Predefinied Iterators - Iterate Command
'----------------------------------------------------------------------
Class CIterateOverI
	Function GoFirst( vNode )
		GoFirst = GoFirstI( vNode )
	End Function
	Function GoNext( vNode )
		GoNext = GoNextI( vNode )
	End Function
End Class
Class CIterateOverJ
	Function GoFirst( vNode )
		GoFirst = GoFirstJ( vNode )
	End Function
	Function GoNext( vNode )
		GoNext = GoNextJ( vNode )
	End Function
End Class
Class CIterateOverK
	Function GoFirst( vNode )
		GoFirst = GoFirstK( vNode )
	End Function
	Function GoNext( vNode )
		GoNext = GoNextK( vNode )
	End Function
End Class
Class CIterateERR
	Function GoFirst( vNode )
		GoFirst = GoFirstERR( vNode )
	End Function
	Function GoNext( vNode )
		GoNext = GoNextERR( vNode )
	End Function
End Class
Class CIterateNIL
	Function GoFirst( vNode )
		GoFirst = False
	End Function
End Class

'----------------------------------------------------------------------
' Predefinied Iterators - ForArray & ForEach Command
'----------------------------------------------------------------------
Class CForItemI
	Function GoItem( vNode, vParam )
		GoItem = OnItemI( vNode, vParam )
	End Function
End Class
Class CForItemJ
	Function GoItem( vNode, vParam )
		GoItem = OnItemJ( vNode, vParam )
	End Function
End Class
Class CForItemK
	Function GoItem( vNode, vParam )
		GoItem = OnItemK( vNode, vParam )
	End Function
End Class

'----------------------------------------------------------------------
' Predefined Tree Node for the Tree Component
'----------------------------------------------------------------------
Class CTPTreeNode
	Dim mType, mAttr, mNodes
	
	Sub Class_Initialize()
		mType = "TREE"
		Set mAttr = CreateObject("Scripting.Dictionary")
		mNodes = Array("")
	End Sub

	Property Get NodeType()
		NodeType = mType
	End Property
	Property Let NodeType(Value)
		mType = Value
	End Property

	Property Get Attributes()
		Set Attributes = mAttr
	End Property
	Property Get AttributeKeys()
		AttributeKeys = mAttr.Keys
	End Property
	Property Get HasAttribute(sName)
		HasAttribute = mAttr.Exists(sName)
	End Property
	Property Get Attribute(sName)
		If HasAttribute(LCase(sName)) Then
			Attribute = mAttr(sName)
		Else
			Attribute = ""
		End If
	End Property
	Property Let Attribute(sName,sValue)
		If mAttr.Exists(LCase(sName)) Then
			mAttr.Remove(LCase(sName))
		End If
		mAttr.Add LCase(sName),sValue
	End Property

	Property Get NodeCount()
		NodeCount = UBound(mNodes)
	End Property
	Property Get Nodes()
		Nodes = mNodes
	End Property
	Property Get Node(Index)
		Set Node = mNodes(Index)
	End Property
	Function AddNode( oNode )
		Redim Preserve mNodes(Ubound(mNodes)+1)
		Set mNodes(Ubound(mNodes)) = oNode
		Set AddNode = oNode
	End Function

	Function ToArray()
		Dim mResult, mArr, Idx
		mResult = Array(mType,mAttr,Array(""))
		mArr = Array("")
		Redim mArr(UBound(mNodes))
		For Idx = 1 To UBound(mNodes)
			mArr(Idx) = mNodes(Idx).ToArray()
		Next
		mResult(2) = mArr
		ToArray = mResult
	End Function
	Sub FromArray( mArray )
		Dim mArr, Idx, oNode
		mType(0) = mArray(0) ' type
		Set mAttr(1) = mArray(1) ' attributes
		mArr = mArray(2)
		Redim mNodes(Ubound(mArr))
		For Idx = 1 To Ubound(mArr)
			set mNodes(Idx) = New CTreeNode
			mNodes(Idx).FromArray mArr(Idx)
		Next
	End Sub

End Class
%>